﻿Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Data
Imports System.Drawing
Imports System.Text
Imports System.Text.RegularExpressions
Imports System.Windows.Forms
Imports System.Security.Cryptography
Imports System.Security.Cryptography.X509Certificates

Imports DevScope.CartaoDeCidadao

Partial Public Class frmMain
    Inherits Form
    Private isCardPresent As Boolean
    Private currentReader As String
    Private readerList As List(Of String)
    Private knownCards As New List(Of String)(New String() {"3B 7D 95 00 00 80 31 80 65 B0 83 11 00 C8 83 00 90 00", "3B 7D 95 00 00 80 31 80 65 B0 83 11 C0 A9 83 00 90 00", "3B 95 95 40 FF D0 00 54 01 31", "3B 95 95 40 FF D0 00 54 01 32"})

    Private Const REGEX_ISSUER_NAME As String = "CN=([^,]+),|CN=([^,]+)$"
    Private Const REGEX_SUBJECT_NAME As String = "^CN=([^,]+),"

    Private proxyInfo As ProxyInfo
    Private notesFilePath As Byte() = {&H3F, &H0, &H5F, &H0, CByte(&HEF), &H7}

    Private scWatcher As SCWatcher

    Private Delegate Sub OnSetStatusText(text As String)
    Private Delegate Sub OnShowInfoMessage(caption As String, text As String)
    Private Delegate Sub OnShowErrorMessage(caption As String, text As String)
    Private Delegate Sub OnLoadData()

    Private Sub SetStatusText(text As String)
        If statusStrip1.InvokeRequired Then
            Invoke(New OnSetStatusText(AddressOf SetStatusText), New Object() {text})
        Else
            tslblStatus.Text = text
            statusStrip1.Update()
        End If
    End Sub

    ''' <summary>
    ''' 
    ''' </summary>
    ''' <param name="caption"></param>
    ''' <param name="text"></param>
    Private Sub ShowInfoMessage(caption As String, text As String)
        If Me.InvokeRequired Then
            Invoke(New OnShowInfoMessage(AddressOf ShowInfoMessage), New Object() {caption, text})
        Else
            MessageBox.Show(Me, text, caption, MessageBoxButtons.OK, MessageBoxIcon.Information)
        End If
    End Sub

    Private Sub ShowErrorMessage(caption As String, text As String)
        If Me.InvokeRequired Then
            Invoke(New OnShowErrorMessage(AddressOf ShowErrorMessage), New Object() {caption, text})
        Else
            MessageBox.Show(Me, text, caption, MessageBoxButtons.OK, MessageBoxIcon.[Error])
        End If
    End Sub

    Private Sub LoadData()
        If tabControl1.InvokeRequired Then
            ', , new object[] { selectedPanel });
            Invoke(New OnLoadData(AddressOf LoadData))
        Else
            If Not isCardPresent Then
                ShowInfoMessage("Cartão de Cidadão", "Não foi detectado nenhum cartão de cidadão.")
                Return
            End If
            SetStatusText("A ler dados do cartão...")
            Try
                Cursor = Cursors.WaitCursor
                Select Case tabControl1.SelectedIndex
                    Case 0, 1
                        LoadIdentityData()
                        Exit Select
                    Case 2
                        LoadAddressData()
                        Exit Select
                    Case 3
                        LoadCertificates()
                        Exit Select
                    Case 4
                        LoadPINs()
                        Exit Select
                    Case 5
                        txtNotes.Text = LoadNotes()
                        Exit Select
                    Case Else
                        ' Should never occur, so don't do anyhing
                        Exit Select
                End Select
            Catch ex As EIDPTException
                Select Case ex.ErrorCode
                    Case ResultCode.SC_ERROR_KEYPAD_CANCELLED
                        Exit Select
                    Case ResultCode.SC_ERROR_PIN_CODE_INCORRECT
                        ShowErrorMessage("Pin Inválido", "O pin introduzido é incorrecto.")
                        Exit Select
                    Case Else
                        ShowErrorMessage("Erro ao lêr dados", "Ocorreu um erro ao lêr os dados do cartão." & ex.Message)
                        Exit Select
                End Select
            Finally
                Cursor = Cursors.[Default]
            End Try
            SetStatusText(String.Empty)
        End If
    End Sub

    Public Sub New()
        InitializeComponent()
    End Sub

    Private Function GetCertificateSubjectName(subject As String) As String
        Dim subjectMatch As Match = Regex.Match(subject, REGEX_SUBJECT_NAME)
        If subjectMatch.Success Then
            Return subjectMatch.Groups(1).Value
        End If
        Return String.Empty
    End Function

    Private Function GetCertificateIssuerName(issuer As String) As String
        Dim subjectMatch As Match = Regex.Match(issuer, REGEX_ISSUER_NAME)
        If subjectMatch.Success Then
            If subjectMatch.Groups(1).Success Then
                Return subjectMatch.Groups(1).Value
            End If
            Return subjectMatch.Groups(2).Value
        End If
        Return String.Empty
    End Function

    Private Sub LoadIdentityData()
        Dim citizen As Id = EIDPT.GetID()
        Dim picture As Picture = EIDPT.GetPicture()

        Dim ms As New System.IO.MemoryStream(picture.Bytes, 0, picture.BytesLength, False)
        ' JPEG2000 Support provided by CSJ2K (http://csj2k.codeplex.com/)
        Dim tempImage As Image = CSJ2K.J2kImage.FromStream(ms)
        ms.Close()
        pbPhoto.Image = tempImage
        ' Citizen Identity Data
        txtName.Text = citizen.Name
        txtFirstName.Text = citizen.FirstName
        txtSex.Text = citizen.Sex
        txtHeight.Text = citizen.Height
        txtNationality.Text = citizen.Nationality
        txtBirthDate.Text = citizen.BirthDate
        txtDocumentNumber.Text = citizen.CardNumber
        txtExpiryDate.Text = citizen.ExpiryDate
        txtCountry.Text = citizen.Country
        txtParents.Text = String.Format("{0} {1}" & vbCr & vbLf & "{2} {3}", citizen.FirstNameFather, citizen.NameFather, citizen.FirstNameMother, citizen.NameMother)
        txtCitizenNote.Text = citizen.Notes

        ' Other Card Data
        txtNIF.Text = citizen.NIF
        txtNSS.Text = citizen.NSS
        txtSNS.Text = citizen.SNS
        txtCardVersion.Text = citizen.CardVersion
        txtIssuingDate.Text = citizen.DeliveryDate
        txtIssuingEntity.Text = citizen.DeliveryEntity
        txtDocumentType.Text = citizen.DocumentType
        txtRequestPlace.Text = citizen.Locale
        If EIDPT.IsActivated() = CardActivationStatus.ACTIVE Then
            lnkCardActivation.Text = "O Cartão de Cidadão encontra-se activo."
        Else
            lnkCardActivation.Text = "O Cartão de Cidadão não se encontra activo."
        End If
    End Sub

    Private Sub LoadAddressData()
        Dim address As Address = EIDPT.GetAddress()
        txtDistrict.Text = address.DistrictDescription
        txtMunicipality.Text = address.MunicipalityDescription
        txtFreguesia.Text = address.FreguesiaDescription
        txtAbrStreetType.Text = address.StreetTypeAbbreviation
        txtStreetType.Text = address.StreetType
        txtStreet.Text = address.Street
        txtAbrBuilding.Text = address.BuildingAbbreviation
        txtBuilding.Text = address.Building
        txtDoor.Text = address.Door
        txtFloor.Text = address.Floor
        txtSide.Text = address.Side
        txtRegion.Text = address.Place
        txtLocality.Text = address.Locality
        txtZIP4.Text = address.CP4
        txtZIP3.Text = address.CP3
        txtPostalLocality.Text = address.Postal
    End Sub

    Private Sub LoadCertificates()
        Dim certificates As Certificate() = EIDPT.GetCertificates()
        tvCertificates.Nodes.Clear()
        ' .Items.Clear();
        For Each certificate As Certificate In certificates
            Dim xCertificate As New X509Certificate(certificate.Bytes)
            Dim newNode As New TreeNode()
            newNode.Text = Me.GetCertificateSubjectName(xCertificate.Subject)
            newNode.Tag = xCertificate
            tvCertificates.Nodes.Add(newNode)
        Next
    End Sub

    Private Sub LoadPINs()
        Dim pins As Pin() = EIDPT.GetPINs()
        tvPINs.Nodes.Clear()
        For Each pin As Pin In pins
            Dim newNode As New TreeNode(pin.Label)
            newNode.Tag = pin
            tvPINs.Nodes.Add(newNode)
        Next
    End Sub

    Private Function LoadNotes() As String
        Dim fileBytes As Byte() = EIDPT.ReadFile(notesFilePath, CByte(PinId.AUTHORIZATION_PIN_ID))
        Return System.Text.Encoding.UTF8.GetString(fileBytes)
    End Function

    Private Sub SaveNotes(notes As String)
        Dim fileBytes As [Byte]() = System.Text.Encoding.UTF8.GetBytes(notes)
        EIDPT.WriteFile(notesFilePath, fileBytes, CByte(PinId.AUTHORIZATION_PIN_ID))
    End Sub

    Private Sub ChangeAddressHandler(state As AddressChangeState)
        ShowInfoMessage("Estado da alteração da morada", "Estado actual: " & state.ToString())
    End Sub

    Private Sub ChangePinHandler(state As CapPinChangeState)
        ShowInfoMessage("Estado da alteração do PIN", "Estado actual: " & state.ToString())
    End Sub

    Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        proxyInfo = New ProxyInfo()
        scWatcher = scWatcher.GetInstance()
        ' Daemon starts when instantiated
        Me.currentReader = String.Empty
        Me.readerList = New List(Of String)(scWatcher.ListReaders())
        If Me.readerList.Count > 0 Then
            Me.currentReader = Me.readerList(0)
        End If
        AddHandler scWatcher.CardInserted, New SCWatcher.CardInsertedHandler(AddressOf scWatcher_CardInserted)
        AddHandler scWatcher.CardRemoved, New SCWatcher.CardRemovedHandler(AddressOf scWatcher_CardRemoved)
        AddHandler scWatcher.ReaderInserted, New SCWatcher.ReaderInsertedHandler(AddressOf scWatcher_ReaderInserted)
        AddHandler scWatcher.ReaderRemoved, New SCWatcher.ReaderRemovedHandler(AddressOf scWatcher_ReaderRemoved)
        AddHandler scWatcher.OnError, New SCWatcher.ErrorHandler(AddressOf scWatcher_OnError)
        ' TODO: Test and validate the use of delegate as function pointer
        Dim eId As EIDPT = EIDPT.GetInstance()
        eId.ChangeAddressHandler = DirectCast([Delegate].Combine(eId.ChangeAddressHandler, New EIDPT.ChangeAddressCallback(AddressOf ChangeAddressHandler)), EIDPT.ChangeAddressCallback)
        eId.ChangePinHandler = DirectCast([Delegate].Combine(eId.ChangePinHandler, New EIDPT.CapPinChangeCallback(AddressOf ChangePinHandler)), EIDPT.CapPinChangeCallback)
    End Sub

    Private Sub scWatcher_CardInserted(readerName As String, cardName As String)
        ShowInfoMessage("Cartão inserido", "Um cartão foi inserido." & vbCr & vbLf & "Nome (ATR) = " & cardName)
        If readerName <> Me.currentReader OrElse Me.isCardPresent Then
            Return
        End If

        ShowInfoMessage("Cartão de Cidadão", "Foi detectado um cartão de cidadão.")

        Try
            SetStatusText("A inicializar o acesso ao cartão...")
            EIDPT.Init(readerName)
            EIDPT.SetSODChecking(False)
        Catch ex As EIDPTException
            SetStatusText(String.Empty)
            EIDPT.[Exit](ExitMode.LEAVE_CARD)
            ShowErrorMessage("Erro a inicializar o acesso ao cartão", ex.Message)
            Return
        End Try

        SetStatusText(String.Empty)
        Me.currentReader = readerName
        Me.isCardPresent = True
        LoadData()
        ' Should resume presentation on UI Thread
    End Sub

    Private Sub scWatcher_CardRemoved(readerName As String)
        ShowInfoMessage("Cartão removido", "Um cartão foi removido." & vbCr & vbLf & "Nome do leitor: " & readerName)
        If Not Me.isCardPresent Then
            Return
        End If
        If Me.currentReader <> readerName Then
            Return
        End If

        ShowInfoMessage("Cartão de Cidadão", "O cartão de cidadão foi removido.")

        EIDPT.[Exit](ExitMode.LEAVE_CARD)
        Me.isCardPresent = False
    End Sub

    Private Sub scWatcher_ReaderInserted(readerName As String)
        ShowInfoMessage("Leitor de cartões", "Um leitor foi ligado." & vbCr & vbLf & "Nome do leitor: " & readerName)
        If Not readerList.Contains(readerName) Then
            Me.readerList.Add(readerName)
        End If

        If Me.currentReader.Length = 0 Then
            Me.currentReader = readerName
        End If
    End Sub

    Private Sub scWatcher_ReaderRemoved(readerName As String)
        ShowInfoMessage("Leitor de cartões", "Um leitor foi desligado." & vbCr & vbLf & "Nome do leitor: " & readerName)
        If readerList.Contains(readerName) Then
            readerList.Remove(readerName)
        End If

        If Me.currentReader <> readerName Then
            Return
        End If

        EIDPT.[Exit](ExitMode.LEAVE_CARD)
        Me.isCardPresent = False
        Me.currentReader = String.Empty
    End Sub

    Private Sub scWatcher_OnError(errorCode As Integer, errorMessage As String)
        ShowErrorMessage("Erro ao monitorizar leitores e cartões", errorMessage)
    End Sub

    Private Sub frmMain_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
        SetStatusText("A terminar a monitorização de leitores e cartões...")
        Me.scWatcher.[Stop]()
        If Me.isCardPresent Then
            EIDPT.[Exit](ExitMode.LEAVE_CARD)
        End If
    End Sub

    Private Sub tabControl1_Selecting(sender As Object, e As TabControlCancelEventArgs) Handles tabControl1.Selecting
        LoadData()
    End Sub

    Private Sub readDataMenuItem_Click(sender As Object, e As EventArgs) Handles readDataMenuItem.Click
        LoadData()
    End Sub

    Private Sub exitMenuItem_Click(sender As Object, e As EventArgs) Handles exitMenuItem.Click
        Me.Close()
    End Sub

    Private Sub settingsMenuItem_Click(sender As Object, e As EventArgs) Handles settingsMenuItem.Click
        Dim settingsForm As New frmSettings(Me.readerList, Me.currentReader, Me.proxyInfo)
        AddHandler settingsForm.UpdateSettings, New frmSettings.UpdateSettingsHandler(AddressOf settingsForm_UpdateSettings)
        settingsForm.ShowDialog(Me)
    End Sub

    Private Sub settingsForm_UpdateSettings(sender As Object, e As UpdateSettingsEventArgs)
        Me.currentReader = e.SelectedReader
        Me.proxyInfo = e.ProxyConfiguration
    End Sub

    Private Sub aboutMenuItem_Click(sender As Object, e As EventArgs) Handles aboutMenuItem.Click
        Dim aboutForm As New frmAbout()
        aboutForm.ShowDialog(Me)
    End Sub

    Private Sub tvCertificates_NodeMouseClick(sender As Object, e As TreeNodeMouseClickEventArgs) Handles tvPINs.NodeMouseClick
        Dim xCertificate As X509Certificate = DirectCast(e.Node.Tag, X509Certificate)
        txtCertificateSubject.Text = Me.GetCertificateSubjectName(xCertificate.Subject)
        txtCertificateIssuer.Text = Me.GetCertificateIssuerName(xCertificate.Issuer)
        txtCertificateDate.Text = xCertificate.GetEffectiveDateString()
        txtCertificateExpirationDate.Text = xCertificate.GetExpirationDateString()
        Dim xCertificate2 As New X509Certificate2(xCertificate.GetRawCertData())
        Dim rsa As RSA = DirectCast(xCertificate2.PublicKey.Key, RSACryptoServiceProvider)
        txtCertificateKeySize.Text = rsa.KeySize & " bits"
        txtCertificateSerialNumber.Text = xCertificate2.SerialNumber

    End Sub

    Private Sub btnShowCertificate_Click(sender As Object, e As EventArgs) Handles btnShowCertificate.Click
        If tvCertificates.SelectedNode Is Nothing Then
            Return
        End If
        Dim xCertificate As X509Certificate = DirectCast(tvCertificates.SelectedNode.Tag, X509Certificate)
        Dim xCertificate2 As New X509Certificate2(xCertificate.GetRawCertData())
        'System.Security.Cryptography.X509Certificates.X509Certificate2UI.DisplayCertificate(xCertificate2)
    End Sub

    Private Sub btnValidateCertificate_Click(sender As Object, e As EventArgs) Handles btnValidateCertificate.Click
        If tvCertificates.SelectedNode Is Nothing Then
            Return
        End If
        Dim xCertificate As X509Certificate = DirectCast(tvCertificates.SelectedNode.Tag, X509Certificate)
        Dim xCertificate2 As New X509Certificate2(xCertificate.GetRawCertData())
        Dim chain As New X509Chain()
        chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot
        chain.ChainPolicy.RevocationMode = X509RevocationMode.Online
        chain.ChainPolicy.UrlRetrievalTimeout = New TimeSpan(0, 0, 60)
        chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag
        SetStatusText("A validar o certificado...")
        Try
            If chain.Build(xCertificate2) Then
                MessageBox.Show(Me, "O certificado encontra-se válido.", "Informação", MessageBoxButtons.OK, MessageBoxIcon.Information)
            Else
                MessageBox.Show(Me, "O certificado não é válido.", "Informação", MessageBoxButtons.OK, MessageBoxIcon.Warning)
            End If
        Catch ex As Exception
            MessageBox.Show(Me, "Ocorreu um erro ao validar o certificado." & vbCr & vbLf & "Detalhes: " & ex.Message, "Erro", MessageBoxButtons.OK, MessageBoxIcon.[Error])
        End Try
        SetStatusText(String.Empty)
    End Sub

    Private Sub tvPINs_AfterSelect(sender As Object, e As TreeViewEventArgs) Handles tvPINs.AfterSelect
        Dim pin As Pin = DirectCast(e.Node.Tag, Pin)
        txtPINType.Text = pin.Type.ToString()
        txtPINId.Text = pin.Id.ToString()
        txtPINState.Text = "Resta(m) " & pin.TriesLeft & " tentativa(s)"
    End Sub

    Private Sub btnValidatePIN_Click(sender As Object, e As EventArgs) Handles btnValidatePIN.Click
        If tvPINs.SelectedNode Is Nothing Then
            Return
        End If
        Dim pin As Pin = DirectCast(tvPINs.SelectedNode.Tag, Pin)
        SetStatusText("A validar o PIN...")
        Try
            pin.TriesLeft = EIDPT.VerifyPINNoAlert(pin.Id, Nothing)
            txtPINState.Text = "Resta(m) " & pin.TriesLeft & " tentativa(s)"
            tvPINs.SelectedNode.Tag = pin
        Catch ex As EIDPTException
            Select Case ex.ErrorCode
                Case ResultCode.SC_ERROR_KEYPAD_CANCELLED
                    Exit Select
                Case ResultCode.SC_ERROR_PIN_CODE_INCORRECT
                    MessageBox.Show(Me, "O pin introduzido é incorrecto. Já só tem " & pin.TriesLeft & " tentativa(s).", "Pin Inválido", MessageBoxButtons.OK, MessageBoxIcon.Warning)
                    Exit Select
                Case Else
                    MessageBox.Show(Me, ex.Message, "Erro ao validar o PIN", MessageBoxButtons.OK, MessageBoxIcon.[Error])
                    Exit Select
            End Select
        End Try
        SetStatusText(String.Empty)
    End Sub

    Private Sub btnChangePIN_Click(sender As Object, e As EventArgs) Handles btnChangePIN.Click
        If tvPINs.SelectedNode Is Nothing Then
            Return
        End If
        Dim pin As Pin = DirectCast(tvPINs.SelectedNode.Tag, Pin)
        SetStatusText("A solicitar alteração do PIN...")
        Try
            'EIDPT.UnblockPIN_Ext(pin.Id, "123", "1234", (uint)PinUnblockType.NEW_PIN);
            pin.TriesLeft = EIDPT.ChangePIN(pin.Id, Nothing, Nothing)
            txtPINState.Text = "Resta(m) " & pin.TriesLeft & " tentativa(s)"
            tvPINs.SelectedNode.Tag = pin
        Catch ex As EIDPTException
            Select Case ex.ErrorCode
                Case ResultCode.SC_ERROR_KEYPAD_CANCELLED
                    Exit Select
                Case ResultCode.SC_ERROR_PIN_CODE_INCORRECT
                    MessageBox.Show(Me, "O pin actual introduzido é incorrecto. Já só tem " & pin.TriesLeft & " tentativa(s).", "Pin Inválido", MessageBoxButtons.OK, MessageBoxIcon.Warning)
                    Exit Select
                Case Else
                    MessageBox.Show(Me, ex.Message, "Erro ao alterar o PIN", MessageBoxButtons.OK, MessageBoxIcon.[Error])
                    Exit Select
            End Select
        End Try
        SetStatusText(String.Empty)
    End Sub

    Private Sub btnSaveNotes_Click(sender As Object, e As EventArgs) Handles btnSaveNotes.Click
        If Not isCardPresent Then
            MessageBox.Show("Não foi detectado nenhum cartão de cidadão.", "Informação", MessageBoxButtons.OK, MessageBoxIcon.Information)
            Return
        End If
        SetStatusText("A guardar dados no cartão...")
        Try
            SaveNotes(txtNotes.Text)
        Catch ex As EIDPTException
            MessageBox.Show("Ocorreu um erro ao guardar os dados." & ex.Message, "Erro ao gravar dados", MessageBoxButtons.OK, MessageBoxIcon.[Error])
        End Try
        SetStatusText(String.Empty)
    End Sub

    Private Sub txtNotes_TextChanged(sender As Object, e As EventArgs) Handles txtNotes.Click
        Dim fileBytes As [Byte]() = System.Text.Encoding.UTF8.GetBytes(txtNotes.Text)
        txtNotesRemainingBytes.Text = (1000 - fileBytes.Length \ 4).ToString()
    End Sub
End Class
